import { CallbackManagerForToolRun } from "@langchain/core/callbacks/manager";
import { Tool, type ToolParams } from "@langchain/core/tools";
import { getEnvironmentVariable } from "@langchain/core/utils/env";

/**
 * Options for the TavilySearchResults tool. (Deprecated)
 *
 * @deprecated Please use the `TavilySearch` tool from the `@langchain/tavily` package, instead.
 */
export type TavilySearchAPIRetrieverFields = ToolParams & {
  /**
   * The maximum number of search results to return.
   *
   * @default 5
   */
  maxResults?: number;

  /**
   * Additional keyword arguments to pass to the API.
   */
  kwargs?: Record<string, unknown>;

  /**
   * The API key used for authentication with the Tavily Search API.
   *
   */
  apiKey?: string;

  /**
   * Include a list of query-related images in the response.
   *
   * @default false
   */
  includeImages?: boolean;

  /**
   * When includeImages is set to True, this option adds descriptive text for each image.
   *
   * @default false
   */
  includeImageDescriptions?: boolean;

  /**
   * Include a short answer to the original query.
   *
   * @default false
   */
  includeAnswer?: boolean;

  /**
   * Include the cleaned and parsed HTML content of each search result.
   *
   * @default false
   */
  includeRawContent?: boolean;

  /**
   * A list of domains to specifically include in the search results.
   *
   * @default []
   */
  includeDomains?: string[];

  /**
   * A list of domains to specifically exclude from the search results.
   *
   * @default []
   */
  excludeDomains?: string[];

  /**
   * The depth of the search. It can be "basic" or "deep".
   *
   * @default "basic"
   */
  searchDepth?: "basic" | "deep";

  /**
   * The category of the search. This will determine which of our agents will be used for the search. Currently, only "general" and "news" are supported. See https://docs.tavily.com/docs/rest-api/api-reference
   *
   * @default "general"
   */
  topic?: string;

  /**
   * The number of days back from the current date to include in the search results.
   *
   * @default 3
   */
  days?: number;

  /**
   * The base API url used for the Tavily Search API.
   *
   * @default "https://api.tavily.com"
   */
  apiUrl?: string;
};

/**
 * Tavily search API tool integration. (Deprecated)
 *
 * @deprecated Please use the `TavilySearch` tool from the `@langchain/tavily` package, instead.
 *
 * Setup:
 * Install `@langchain/community`. You'll also need an API key set as `TAVILY_API_KEY`.
 *
 * ```bash
 * npm install @langchain/community
 * ```
 *
 * ## [Constructor args](https://api.js.langchain.com/classes/_langchain_community.tools_tavily_search.TavilySearchResults.html#constructor)
 *
 * <details open>
 * <summary><strong>Instantiate</strong></summary>
 *
 * ```typescript
 * import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
 *
 * const tool = new TavilySearchResults({
 *   maxResults: 2,
 *   // ...
 * });
 * ```
 * </details>
 *
 * <br />
 *
 * <details>
 *
 * <summary><strong>Invocation</strong></summary>
 *
 * ```typescript
 * await tool.invoke("what is the current weather in sf?");
 * ```
 * </details>
 *
 * <br />
 *
 * <details>
 *
 * <summary><strong>Invocation with tool call</strong></summary>
 *
 * ```typescript
 * // This is usually generated by a model, but we'll create a tool call directly for demo purposes.
 * const modelGeneratedToolCall = {
 *   args: {
 *     input: "what is the current weather in sf?",
 *   },
 *   id: "tool_call_id",
 *   name: tool.name,
 *   type: "tool_call",
 * };
 * await tool.invoke(modelGeneratedToolCall);
 * ```
 *
 * ```text
 * ToolMessage {
 *   "content": "...",
 *   "name": "tavily_search_results_json",
 *   "additional_kwargs": {},
 *   "response_metadata": {},
 *   "tool_call_id": "tool_call_id"
 * }
 * ```
 * </details>
 */
export class TavilySearchResults extends Tool {
  static lc_name(): string {
    return "TavilySearchResults";
  }

  description =
    "A search engine optimized for comprehensive, accurate, and trusted results. Useful for when you need to answer questions about current events. Input should be a search query.";

  name = "tavily_search_results_json";

  protected maxResults = 5;

  protected apiKey?: string;

  protected kwargs: Record<string, unknown> = {};

  protected includeImages?: boolean;

  protected includeImageDescriptions?: boolean;

  protected includeAnswer?: boolean;

  protected includeRawContent?: boolean;

  protected includeDomains?: string[];

  protected excludeDomains?: string[];

  protected searchDepth?: "basic" | "deep";

  protected topic?: string;

  protected days?: number;

  protected apiUrl?: string;

  constructor(fields?: TavilySearchAPIRetrieverFields) {
    super(fields);
    this.maxResults = fields?.maxResults ?? this.maxResults;
    this.kwargs = fields?.kwargs ?? this.kwargs;
    this.apiKey = fields?.apiKey ?? getEnvironmentVariable("TAVILY_API_KEY");
    this.includeImages = fields?.includeImages ?? this.includeImages;
    this.includeImageDescriptions =
      fields?.includeImageDescriptions ?? this.includeImageDescriptions;
    this.includeAnswer = fields?.includeAnswer ?? this.includeAnswer;
    this.includeRawContent =
      fields?.includeRawContent ?? this.includeRawContent;
    this.includeDomains = fields?.includeDomains ?? this.includeDomains;
    this.excludeDomains = fields?.excludeDomains ?? this.excludeDomains;
    this.searchDepth = fields?.searchDepth ?? this.searchDepth;
    this.topic = fields?.topic ?? this.topic;
    this.days = fields?.days ?? this.days;
    this.apiUrl = fields?.apiUrl ?? "https://api.tavily.com";

    if (this.apiKey === undefined) {
      throw new Error(
        `No Tavily API key found. Either set an environment variable named "TAVILY_API_KEY" or pass an API key as "apiKey".`
      );
    }
  }

  protected async _call(
    input: string,
    _runManager?: CallbackManagerForToolRun
  ): Promise<string> {
    const body: Record<string, unknown> = {
      query: input,
      max_results: this.maxResults,
      include_images: this.includeImages,
      include_image_descriptions: this.includeImageDescriptions,
      include_answer: this.includeAnswer,
      include_raw_content: this.includeRawContent,
      include_domains: this.includeDomains,
      exclude_domains: this.excludeDomains,
      search_depth: this.searchDepth,
      topic: this.topic,
      days: this.days,
    };

    const response = await fetch(`${this.apiUrl}/search`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: `Bearer ${this.apiKey}`,
      },
      body: JSON.stringify({ ...body, ...this.kwargs }),
    });
    const json = await response.json();
    if (!response.ok) {
      throw new Error(
        `Request failed with status code ${response.status}: ${json.error}`
      );
    }
    if (!Array.isArray(json.results)) {
      throw new Error(`Could not parse Tavily results. Please try again.`);
    }
    return JSON.stringify(json.results);
  }
}
